if (!globalThis.print) {
    globalThis.print = console.log;
}

const TEST_ITERATIONS = 50000;
const BASIC_TEST_ITERATIONS = TEST_ITERATIONS;
const BASIC_TEST_OBJECT_SIZES = [10, 100];
const PROTOTYPE_TEST_ITERATIONS = TEST_ITERATIONS;
const PROTOTYPE_TEST_DEPTHS = [1, 3, 5, 10];
const PROTOTYPE_TEST_PROPS_PER_LEVEL = 10;
const SYMBOL_TEST_ITERATIONS = TEST_ITERATIONS;
const SYMBOL_TEST_NORMAL_PROPS = 100;
const SYMBOL_TEST_SYMBOL_PROPS = 10;
const NON_EXISTING_PROP = "newProperty";
const FIRST_PROP = "prop0";

const ARRAY_TEST_ITERATIONS = TEST_ITERATIONS;
const ARRAY_TEST_SIZES = [10, 100, 1000];
const NON_EXISTING_INDEX = 9999;

function createTestObject(size) {
    const obj = {};
    for (let i = 0; i < size; i++) {
        obj[`prop${i}`] = i;
    }
    return obj;
}

function createTestArray(size) {
    const arr = [];
    for (let i = 0; i < size; i++) {
        arr[i] = i;
    }
    return arr;
}

function createObjectWithPrototype(protoDepth, propsPerLevel) {
    let obj = {};
    let current = obj;

    for (let depth = 0; depth < protoDepth; depth++) {
        const proto = {};
        for (let i = 0; i < propsPerLevel; i++) {
            proto[`proto${depth}_prop${i}`] = i;
        }
        Object.setPrototypeOf(current, proto);
        current = proto;
    }

    return obj;
}

function createObjectWithSymbols(normalProps, symbolProps) {
    const obj = {};
    const symbols = [];

    for (let i = 0; i < normalProps; i++) {
        obj[`prop${i}`] = i;
    }

    for (let i = 0; i < symbolProps; i++) {
        const sym = Symbol(`sym${i}`);
        obj[sym] = i;
        symbols.push(sym);
    }

    return { obj, symbols };
}

function testReflectSet(obj, iterations, propToSet, value) {
    const startTime = Date.now();
    const testObj = { ...obj };

    for (let i = 0; i < iterations; i++) {
        Reflect.set(testObj, propToSet, value);
    }

    const endTime = Date.now();
    return endTime - startTime;
}

function formatTestResult(testName, time) {
    return `TEST_RESULT: ${testName} | TIME: ${time.toFixed(2)}`;
}

function runBasicTests() {
    print("=== BASIC TESTS ===");
    print(`Running performance tests with ${BASIC_TEST_ITERATIONS} iterations`);
    print("----------------------------------------");

    for (const size of BASIC_TEST_OBJECT_SIZES) {
        print(`Object size: ${size} properties`);
        const testObj = createTestObject(size);

        const reflectTimeExisting = testReflectSet(
            testObj,
            BASIC_TEST_ITERATIONS,
            FIRST_PROP,
            42
        );
        print(
            formatTestResult(
                `Basic_Size${size}_ExistingFirstProp`,
                reflectTimeExisting
            )
        );

        const lastProp = `prop${size - 1}`;
        const reflectTimeLast = testReflectSet(
            testObj,
            BASIC_TEST_ITERATIONS,
            lastProp,
            42
        );
        print(
            formatTestResult(
                `Basic_Size${size}_ExistingLastProp`,
                reflectTimeLast
            )
        );

        const reflectTimeNew = testReflectSet(
            testObj,
            BASIC_TEST_ITERATIONS,
            NON_EXISTING_PROP,
            42
        );
        print(formatTestResult(`Basic_Size${size}_NewProp`, reflectTimeNew));
        print("----------------------------------------");
    }
}

function runArrayTests() {
    print("=== ARRAY TESTS ===");
    print(`Running performance tests with ${ARRAY_TEST_ITERATIONS} iterations`);
    print("----------------------------------------");

    for (const size of ARRAY_TEST_SIZES) {
        print(`Array size: ${size} elements`);
        const testArray = createTestArray(size);

        const reflectTimeFirst = testReflectSet(
            testArray,
            ARRAY_TEST_ITERATIONS,
            0,
            42
        );
        print(
            formatTestResult(`Array_Size${size}_FirstElement`, reflectTimeFirst)
        );

        const lastIndex = size - 1;
        const reflectTimeLast = testReflectSet(
            testArray,
            ARRAY_TEST_ITERATIONS,
            lastIndex,
            42
        );
        print(
            formatTestResult(`Array_Size${size}_LastElement`, reflectTimeLast)
        );

        const middleIndex = Math.floor(size / 2);
        const reflectTimeMiddle = testReflectSet(
            testArray,
            ARRAY_TEST_ITERATIONS,
            middleIndex,
            42
        );
        print(
            formatTestResult(
                `Array_Size${size}_MiddleElement`,
                reflectTimeMiddle
            )
        );

        const reflectTimeNew = testReflectSet(
            testArray,
            ARRAY_TEST_ITERATIONS,
            NON_EXISTING_INDEX,
            42
        );
        print(formatTestResult(`Array_Size${size}_NewElement`, reflectTimeNew));

        const reflectTimeStringIndex = testReflectSet(
            testArray,
            ARRAY_TEST_ITERATIONS,
            "0",
            42
        );
        print(
            formatTestResult(
                `Array_Size${size}_StringIndex`,
                reflectTimeStringIndex
            )
        );
        print("----------------------------------------");
    }
}

function runPrototypeTests() {
    print("=== PROTOTYPE CHAIN TESTS ===");
    print(
        `Running performance tests with ${PROTOTYPE_TEST_ITERATIONS} iterations`
    );
    print(`Properties per prototype level: ${PROTOTYPE_TEST_PROPS_PER_LEVEL}`);
    print("----------------------------------------");

    for (const depth of PROTOTYPE_TEST_DEPTHS) {
        print(`Prototype chain depth: ${depth}`);
        const testObj = createObjectWithPrototype(
            depth,
            PROTOTYPE_TEST_PROPS_PER_LEVEL
        );

        const firstLevelProp = "proto0_prop0";
        const reflectTimeFirstLevel = testReflectSet(
            testObj,
            PROTOTYPE_TEST_ITERATIONS,
            firstLevelProp,
            42
        );
        print(
            formatTestResult(
                `Prototype_Depth${depth}_FirstLevelProp`,
                reflectTimeFirstLevel
            )
        );

        const deepestLevelProp = `proto${depth - 1}_prop0`;
        const reflectTimeDeepest = testReflectSet(
            testObj,
            PROTOTYPE_TEST_ITERATIONS,
            deepestLevelProp,
            42
        );
        print(
            formatTestResult(
                `Prototype_Depth${depth}_DeepestLevelProp`,
                reflectTimeDeepest
            )
        );

        const reflectTimeNew = testReflectSet(
            testObj,
            PROTOTYPE_TEST_ITERATIONS,
            NON_EXISTING_PROP,
            42
        );
        print(
            formatTestResult(`Prototype_Depth${depth}_NewProp`, reflectTimeNew)
        );
        print("----------------------------------------");
    }
}

function runSymbolTests() {
    print("=== SYMBOL PROPERTY TESTS ===");
    print(
        `Running performance tests with ${SYMBOL_TEST_ITERATIONS} iterations`
    );
    print(
        `Normal properties: ${SYMBOL_TEST_NORMAL_PROPS}, Symbol properties: ${SYMBOL_TEST_SYMBOL_PROPS}`
    );
    print("----------------------------------------");

    const { obj: testObj, symbols } = createObjectWithSymbols(
        SYMBOL_TEST_NORMAL_PROPS,
        SYMBOL_TEST_SYMBOL_PROPS
    );

    const reflectTimeNormal = testReflectSet(
        testObj,
        SYMBOL_TEST_ITERATIONS,
        FIRST_PROP,
        42
    );
    print(formatTestResult("Symbol_NormalProperty", reflectTimeNormal));

    const firstSymbol = symbols[0];
    const reflectTimeFirstSymbol = testReflectSet(
        testObj,
        SYMBOL_TEST_ITERATIONS,
        firstSymbol,
        42
    );
    print(
        formatTestResult("Symbol_FirstSymbolProperty", reflectTimeFirstSymbol)
    );

    const lastSymbol = symbols[symbols.length - 1];
    const reflectTimeLastSymbol = testReflectSet(
        testObj,
        SYMBOL_TEST_ITERATIONS,
        lastSymbol,
        42
    );
    print(formatTestResult("Symbol_LastSymbolProperty", reflectTimeLastSymbol));

    const newSymbol = Symbol("new");
    const reflectTimeNewSymbol = testReflectSet(
        testObj,
        SYMBOL_TEST_ITERATIONS,
        newSymbol,
        42
    );
    print(formatTestResult("Symbol_NewSymbolProperty", reflectTimeNewSymbol));
    print("----------------------------------------");
}

print("PERFORMANCE TEST: Reflect.set");
print("============================================");
runBasicTests();
print();
runArrayTests();
print();
runPrototypeTests();
print();
runSymbolTests();
